package org.zfx.utils;

import com.alibaba.fastjson.JSONObject;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jgit.api.*;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.lib.*;
import org.eclipse.jgit.merge.ResolveMerger;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.*;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.util.FS;
import org.zfx.vo.gitController.GetLogsResponse;
import org.zfx.vo.gitController.PullResponse;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
public class GitUtils {

//    public String jgitRsa;
    private SshSessionFactory sshSessionFactory;

    public GitUtils(String jgitRsa){
//        this.jgitRsa = jgitRsa;
        setSshSessionFactory(jgitRsa);
    }

    private void setSshSessionFactory(String jgitRsa){
        this.sshSessionFactory = new JschConfigSessionFactory() {
            @Override
            protected void configure(OpenSshConfig.Host host, Session session) {
                session.setConfig("StrictHostKeyChecking", "no");
            }
            @Override
            protected JSch createDefaultJSch(FS fs) throws JSchException {
                JSch jSch = super.createDefaultJSch(fs);
                jSch.addIdentity(jgitRsa);
                return jSch;
            }
        };
    }

    /**
     * 获取jGit的CredentialsProvider
     * @param userName git用户名
     * @param password git密码
     * @return
     */
    public CredentialsProvider createCredential(String userName, String password) {
        return new UsernamePasswordCredentialsProvider(userName, password);
    }

    /**
     * clone远程仓库 <br/>
     * git clone {repoUrl}
     * @param repoUrl 远程仓库地址
     * @param cloneDir 本地地址
     * @param provider  jGit的CredentialsProvider
     * @return git实例
     * @throws GitAPIException
     */
    public Git fromCloneRepository(String repoUrl, String cloneDir, CredentialsProvider provider) throws GitAPIException {
        return Git.cloneRepository()
                .setCredentialsProvider(provider)
                .setURI(repoUrl)
                .setDirectory(new File(cloneDir)).call();
    }

    /**
     * 添加远程连接
     * @param repoUrl 服务器地址url
     * @param dir 本地磁盘路径
     * @param provider 账号密码生成的CredentialsProvider
     * @throws GitAPIException
     * @throws IOException
     * @throws URISyntaxException
     */
    public void remote(String repoUrl, String dir, CredentialsProvider provider) throws GitAPIException, IOException, URISyntaxException {
        try {
            RemoteAddCommand remoteAddCommand = getGit(dir).remoteAdd();
            remoteAddCommand.setUri(new URIish(repoUrl));
            remoteAddCommand.setName("origin");
            remoteAddCommand.call();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public StringBuffer diffMethod(Git git) throws IOException {
        Repository repository = git.getRepository();
        List<ObjectId> list = new ArrayList<>();
        StringBuffer outputStreamString = new StringBuffer();
        try {
            Collection<ReflogEntry> call_new = git.reflog().setRef("origin/master").call();
            ObjectId remoteId = call_new.stream().findFirst().get().getNewId();
            list.add(remoteId);//获取服务器最新objectid 也就是commitId

            Iterable<RevCommit> call_old = git.log().setMaxCount(1).call();
            ObjectId localId = call_old.iterator().next().getTree().getId();
            list.add(localId);
        } catch (GitAPIException e) {
            e.printStackTrace();
        }
        if (list.size() == 2) {
            AbstractTreeIterator newCommit = getAbstractTreeIterator(list.get(0), repository);
            AbstractTreeIterator oldCommit = getAbstractTreeIterator(list.get(1), repository);
            List<DiffEntry> diff = null;
            try {
                diff = git.diff().setOldTree(oldCommit).setNewTree(newCommit).call();
            } catch (GitAPIException e) {
                e.printStackTrace();
            }
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            DiffFormatter diffFormatter = new DiffFormatter(outputStream);
            // 设置比较器为忽略空白字符对比（Ignores all whitespace）
            diffFormatter.setDiffComparator(RawTextComparator.WS_IGNORE_ALL);
            diffFormatter.setRepository(repository);

            if (diff != null){
                for (DiffEntry diffEntry : diff) {
                    // 打印文件差异具体内容
                    diffFormatter.format(diffEntry);
                    outputStreamString.append("<p>").append(outputStream.toString("UTF-8")).append("</p>");
//                    System.out.println("outputStream" + outputStream.toString("UTF-8"));
                    outputStream.reset();
                }
            }
        }

        int startIndex = 0;
        while ((startIndex = outputStreamString.indexOf("\n", startIndex)) != -1) {
            outputStreamString.replace(startIndex, startIndex + 2, "<br/>");
            startIndex += 1;  // 继续查找的起始索引
        }
        return outputStreamString;
    }
    private static AbstractTreeIterator getAbstractTreeIterator(ObjectId objectId, Repository repository) {
        RevWalk revWalk = new RevWalk(repository);
        CanonicalTreeParser treeParser = null;
        try {
            RevTree revTree = revWalk.parseTree(objectId);
            treeParser = new CanonicalTreeParser();
            treeParser.reset(repository.newObjectReader(), revTree.getId());
            revWalk.dispose();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return treeParser;
    }

    /**
     * git 某个coder pull master代码
     */
    public PullResponse pull(String dir,boolean isRebase,CredentialsProvider provider) {
        Git git = null;
        PullCommand pull = null;
        try {
            git = getGit(dir);
            pull = git.pull();
            pull.setRemote("origin");
//        pull.setRemoteBranchName(coder);
            pull.setRemoteBranchName("master");
            pull.setRebase(isRebase);
            pull.setTransportConfigCallback(transport -> {
                if (transport instanceof TransportHttp) {
                    TransportHttp httpTransport = (TransportHttp) transport;
                    httpTransport.setCredentialsProvider(provider);
                } else if (transport instanceof SshTransport) {
                    SshTransport sshTransport = (SshTransport) transport;
                    sshTransport.setSshSessionFactory(sshSessionFactory);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            PullResult callResult = pull.call();
            log.info("pull to {} ,and message is {} ",dir,callResult.getMergeResult());
//        MergeResult mergeResult = callResult.getMergeResult();
//        return mergeResult.toString();

            RebaseResult rebaseResult = callResult.getRebaseResult();
            //获取rebase停止时的当前提交对象。如果rebase成功完成,则为null。
            RevCommit currentCommit = rebaseResult.getCurrentCommit();
            //获取因冲突而停止的路径列表。如果无冲突,则为空列表。
            List<String> conflicts = rebaseResult.getConflicts();
            //获取merge失败的路径及失败原因的映射。如果无失败,则为null。
            Set<Map.Entry<String, ResolveMerger.MergeFailureReason>> entries = rebaseResult.getFailingPaths() == null ? null : rebaseResult.getFailingPaths().entrySet();
            //获取因有未提交变更而停止rebase的路径列表。如果无未提交变更,则为空列表。
            List<String> uncommittedChanges = rebaseResult.getUncommittedChanges();

            String statusName = rebaseResult.getStatus().name();
            if (statusName.equals(RebaseResult.Status.UP_TO_DATE.toString())){
/*          ssh code :
            server master a.hh =a
            local master a.hh=a
            local pull*/
                return PullResponse.builder()
                        .currentCommit(currentCommit)
                        .conflicts(conflicts)
                        .failingPaths(entries)
                        .getUncommittedChanges(uncommittedChanges)
                        .rebaseResultName(statusName)
                        .message("已是最新,无需rebase.")
                        .build();
            } else if(statusName.equals(RebaseResult.Status.FAST_FORWARD.toString())){
/*          ssh code :
            server master a.hh =a1
            local master a.hh=a
            local pull*/
                return PullResponse.builder()
                        .currentCommit(currentCommit)
                        .conflicts(conflicts)
                        .failingPaths(entries)
                        .getUncommittedChanges(uncommittedChanges)
                        .rebaseResultName(statusName)
                        .message("HEAD指向新commit.")
                        .build();
            } else if(statusName.equals(RebaseResult.Status.CONFLICTS.toString())){
                return PullResponse.builder()
                        .rebaseResultName(statusName)
                        .currentCommit(currentCommit)
                        .conflicts(conflicts)
                        .failingPaths(entries)
                        .getUncommittedChanges(uncommittedChanges)
                        .message("目标HEAD的检出失败,产生冲突.")
                        .build();
            } else if(statusName.equals(RebaseResult.Status.UNCOMMITTED_CHANGES.toString())){
                return PullResponse.builder()
                        .rebaseResultName(statusName)
                        .currentCommit(currentCommit)
                        .conflicts(conflicts)
                        .failingPaths(entries)
                        .getUncommittedChanges(uncommittedChanges)
                        .message("仓库包含未提交的变更,rebase不是快进.")
                        .build();
            } else if(statusName.equals(RebaseResult.Status.FAILED.toString())){
                return PullResponse.builder()
                        .rebaseResultName(statusName)
                        .currentCommit(currentCommit)
                        .conflicts(conflicts)
                        .failingPaths(entries)
                        .getUncommittedChanges(uncommittedChanges)
                        .message("rebase失败,HEAD被还原")
                        .build();
            } else if(statusName.equals(RebaseResult.Status.EDIT.toString())){
                return PullResponse.builder()
                        .rebaseResultName(statusName)
                        .currentCommit(currentCommit)
                        .conflicts(conflicts)
                        .failingPaths(entries)
                        .getUncommittedChanges(uncommittedChanges)
                        .message("rebase在交互式rebase的上下文中停止以进行编辑。")
                        .build();
            } else if(statusName.equals(RebaseResult.Status.STOPPED.toString())){
                return PullResponse.builder()
                        .currentCommit(currentCommit)
                        .conflicts(conflicts)
                        .failingPaths(entries)
                        .getUncommittedChanges(uncommittedChanges)
                        .rebaseResultName(statusName)
                        .message("rebase因冲突停止,必须abort、resolve或skip。")
                        .build();
            } else if(statusName.equals(RebaseResult.Status.OK.toString())){
                return PullResponse.builder()
                        .currentCommit(currentCommit)
                        .conflicts(conflicts)
                        .failingPaths(entries)
                        .getUncommittedChanges(uncommittedChanges)
                        .rebaseResultName(statusName)
                        .message("rebase成功")
                        .build();
            } else if(statusName.equals(RebaseResult.Status.ABORTED.toString())){
                return PullResponse.builder()
                        .currentCommit(currentCommit)
                        .conflicts(conflicts)
                        .failingPaths(entries)
                        .getUncommittedChanges(uncommittedChanges)
                        .rebaseResultName(statusName)
                        .message("rebase被取消,HEAD被还原。")
                        .build();
            } else {
                return PullResponse.builder()
                        .currentCommit(currentCommit)
                        .conflicts(conflicts)
                        .failingPaths(entries)
                        .getUncommittedChanges(uncommittedChanges)
                        .rebaseResultName("unknown")
                        .message("出现未知错误.")
                        .build();
            }
        } catch (GitAPIException e) {
            return PullResponse.builder()
                    .message("由于冲突或其他错误导致无法合并成功，请回滚代码")
                    .rebaseResultName("PULL ERROR")
                    .build();
        }

    }

    /**
     * 第一次创建仓库时候的init组合代码。
     * @param dir 磁盘地址
     * @param provider 管理员权限
     * @param coder 程序员名称/账号
     * @throws Exception
     */
    public void initPull(String dir, CredentialsProvider provider, String coder) throws Exception{

        Git git = getGit(dir);
        AddCommand addCommand = git.add().addFilepattern(".");
        addCommand.call();

        PullCommand pull = git.pull();
        pull.setRemote("origin");
        pull.setRemoteBranchName("master");
        pull.setTransportConfigCallback(transport -> {
            if (transport instanceof TransportHttp) {
                TransportHttp httpTransport = (TransportHttp) transport;
                httpTransport.setCredentialsProvider(provider);
            } else if (transport instanceof SshTransport) {
                SshTransport sshTransport = (SshTransport) transport;
                sshTransport.setSshSessionFactory(sshSessionFactory);
            }
        });
        PullResult callResult = pull.call();
        log.info("git init model pull call result" + callResult);
//      以下注释的代码好使，但是现在只使用master分支，所以暂时不需要，日后可以解开注释。
        git.add().addFilepattern(".").call();
        git.commit().setMessage("coder: "+coder+" init").call();

//        git.branchCreate().setName(coder).call();
//        git.checkout().setName(coder).call();
//        git.push()
//                .setTransportConfigCallback(transport -> {
//                    SshTransport sshTransport = (SshTransport) transport;
//                    sshTransport.setSshSessionFactory(sshSessionFactory);
//                })
//                .setCredentialsProvider(provider)
//                .setRemote("origin").setRefSpecs(new RefSpec(coder)).call();
        git.push()
            .setTransportConfigCallback(transport -> {
                if (transport instanceof TransportHttp) {
                    TransportHttp httpTransport = (TransportHttp) transport;
                    httpTransport.setCredentialsProvider(provider);
                } else if (transport instanceof SshTransport) {
                    SshTransport sshTransport = (SshTransport) transport;
                    sshTransport.setSshSessionFactory(sshSessionFactory);
                }
            })
        .setCredentialsProvider(provider)
        .setRemote("origin").setRefSpecs(new RefSpec("master")).call();
    }

    public void addPath(Git git, String path){
        try {
            git.add().addFilepattern(path).call();
        } catch (GitAPIException e) {
            e.printStackTrace();
        }
    }
    public void removeAddPath(Git git){
        try {
            git.reset().addPath(".").call();
        } catch (GitAPIException e) {
            e.printStackTrace();
        }
    }

    public void addPaths(Git git, List<String> paths, CredentialsProvider provider){
        for (String path: paths) {
            addPath(git,path);
        }
    }

    public String commitAndPush(Git git,
                              String message,
                              String email,
                              String coder,
                              CredentialsProvider provider) throws GitAPIException, IOException {
        commit(git,message,email,coder);
//        push(git,coder,provider);
        String pushResult = push(git, "master", provider);
        return pushResult;
    }

    /**
     * git reset --hard origin/master
     * @param git
     */
    public void resetOriginMaster(Git git){
        ResetCommand reset = git.reset();
        reset.setMode(ResetCommand.ResetType.HARD);
        reset.setRef("origin/master");
        try {
            reset.call();
        } catch (GitAPIException e) {
            e.printStackTrace();
        }
    }

    public void resetLocalMaster(Git git){
        ResetCommand reset = git.reset();
        reset.setMode(ResetCommand.ResetType.HARD);
        reset.setRef("refs/heads/master");
        try {
            reset.call();
        } catch (GitAPIException e) {
            e.printStackTrace();
        }
    }

    public void commit(Git git,
                      String message,
                      String email,
                      String coder) throws GitAPIException, IOException {
        PersonIdent personIdent = new PersonIdent(coder,email);
        CommitCommand commit = git.commit();
        commit.setMessage(message);
        commit.setAuthor(personIdent);
        commit.call();

    }

    public JSONObject status(Git git) throws GitAPIException, IOException {

        Status status = git.status().call();

        //conflictingStageState: 有冲突的文件列表及其冲突状态。主要用于解决文件冲突。
        Map<String, IndexDiff.StageState> conflictingStageState = status.getConflictingStageState();

        //changed: 被修改过的文件列表。
        Set<String> changed = status.getChanged();

        //added: 新添加的文件列表。
        Set<String> added = status.getAdded();

        //conflicting: 有冲突的文件列表。
        Set<String> conflicting = status.getConflicting();

        //ignoredNotInIndex: 被忽略但不在索引中的文件列表。
        Set<String> ignoredNotInIndex = status.getIgnoredNotInIndex();

        //missing: 丢失的文件列表。
        Set<String> missing = status.getMissing();

        //modified: 被修改过的文件列表。与changed相同。
        Set<String> modified = status.getModified();

        //untracked: 未跟踪的文件列表。
        Set<String> untracked = status.getUntracked();

        //untrackedFolders: 未跟踪的文件夹列表。
        Set<String> untrackedFolder = status.getUntrackedFolders();

        //removed: 被移除的文件列表。
        Set<String> removed = status.getRemoved();

        JSONObject result = new JSONObject();
        result.put("changed",changed);
        result.put("added",added);
        result.put("conflicting",conflicting);
        result.put("ignoredNotInIndex",ignoredNotInIndex);
        result.put("missing",missing);
        result.put("modified",modified);
        result.put("untracked",untracked);
        result.put("untrackedFolder",untrackedFolder);
        result.put("removed",removed);
        result.put("conflictingStageState",conflictingStageState);

        return result;
    }

    public String push(Git git, String branch, CredentialsProvider provider) throws GitAPIException, IOException {
        if (branch == null) {
            branch = git.getRepository().getBranch();
        }

        Iterable<PushResult> origin = git.push().setTransportConfigCallback(transport -> {
                    if (transport instanceof TransportHttp) {
                        TransportHttp httpTransport = (TransportHttp) transport;
                        httpTransport.setCredentialsProvider(provider);
//                        httpTransport.setPushAtomic(true);
//                        httpTransport.setUseSmartHttp(true);
                    } else if(transport instanceof SshTransport){
                        SshTransport sshTransport = (SshTransport) transport;
                        sshTransport.setSshSessionFactory(sshSessionFactory);
                    }
                }).setCredentialsProvider(provider)
                .setRemote("origin")
//                .setPushAll()
                .setRefSpecs(new RefSpec(branch))
                .call();
        Iterator<PushResult> iterator = origin.iterator();
        StringBuilder stringBuffer = new StringBuilder();
        stringBuffer.append("<p>");
        while(iterator.hasNext()){
            stringBuffer.append("status:").append(iterator.next().getRemoteUpdate("refs/heads/master").getStatus()).append("<br>");
        }
        return stringBuffer.append("</p>").toString();
    }

    /**
     * 读取已有仓库
     * @param dir 本地地址
     * @return 仓库build
     * @throws IOException
     */
    public Repository getRepository(String dir) throws IOException {
        return new FileRepositoryBuilder()
                .setGitDir(Paths.get(dir, ".git").toFile())
                .build();
    }


    /**
     * 读取已有仓库
     * @param dir 本地地址
     * @return 仓库build
     * @throws IOException
     */
    public Repository getRepositoryFromDir(String dir,String username,String email) throws IOException {
        Repository repository = new FileRepositoryBuilder()
                .setGitDir(Paths.get(dir, ".git").toFile())
                .build();
        //获取配置
        StoredConfig config = repository.getConfig();
        //设置用户及邮箱
        config.setString("user",null,"user",username);
        config.setString("user",null,"email",email);
        return repository;
    }

    public Git getGit(String dir) throws Exception {
        return Git.open(new File(dir));
    }

    /**
     * 读取仓库日志
     * @param repository 仓库实例
     * @return 仓库日志的List<String>集合
     * @throws IOException
     */
    public List<GetLogsResponse> getLogs(Repository repository) throws IOException {
        return getLogsSinceCommit(repository, null, null);
    }

    public List<GetLogsResponse> getLogsSinceCommit(Repository repository, String branch, String commit) throws IOException {
        if (branch == null) {
            branch = repository.getBranch();
        }
        Ref head = repository.findRef("refs/heads/" + branch);
        List<GetLogsResponse> commits = new ArrayList<>();
        if (head != null) {
            try (RevWalk revWalk = new RevWalk(repository)) {
                revWalk.markStart(revWalk.parseCommit(head.getObjectId()));
                for (RevCommit revCommit : revWalk) {
                    if (revCommit.getId().getName().equals(commit)) {
                        break;
                    }
                    GetLogsResponse commitMessage = GetLogsResponse.builder()
                            .fullMessage(revCommit.getFullMessage())
                            .commitTimeCurr(String.valueOf(revCommit.getCommitTime()))
                            .commitTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(revCommit.getCommitTime() * 1000L)))
                            .coder(revCommit.getAuthorIdent().getName())
                            .email(revCommit.getAuthorIdent().getEmailAddress())
                            .build();
                    commits.add(commitMessage);
                }
                revWalk.dispose();
            }
        }
        return commits;
    }
}